// the WebSocket main handler
import {
  ON_MESSAGE_PROP_NAME,
  ON_RESULT_PROP_NAME,
  EMIT_EVT
} from '../utils/constants'
import {
  ON_ERROR_PROP_NAME,
  LOGIN_EVENT_NAME,
  LOGOUT_EVENT_NAME,
  ACKNOWLEDGE_REPLY_TYPE,
  EMIT_REPLY_TYPE,
  ERROR_TYPE,
  ON_READY_PROP_NAME
} from 'jsonql-constants'

import extractWsPayload from './extract-ws-payload'
import { createQueryStr } from 'jsonql-utils'

import { getDebug, createEvt } from '../utils'
const debugFn = getDebug('ws-main-handler')

/**
 * under extremely circumstances we might not even have a resolverName, then
 * we issue a global error for the developer to catch it
 * @param {object} ee event emitter
 * @param {string} namespace nsp
 * @param {string} resolverName resolver
 * @param {object} json decoded payload or error object
 */
const errorTypeHandler = (ee, namespace, resolverName, json) => {
  let evt = [namespace]
  if (resolverName) {
    debugFn(`a global error on ${namespace}`)
    evt.push(resolverName)
  }
  evt.push(ON_ERROR_PROP_NAME)
  let evtName = Reflect.apply(createEvt, null, evt)
  // test if there is a data field
  let payload = json.data || json;
  ee.$trigger(evtName, [payload])
}

/**
 * Binding the even to socket normally
 * @param {string} namespace
 * @param {object} ws the nsp
 * @param {object} ee EventEmitter
 * @return {object} promise resolve after the onopen event
 */
export default function wsMainHandlerAction(namespace, ws, ee) {
  // send
  ws.onopen = function() {
    // we just call the onReady
    ee.$call(ON_READY_PROP_NAME, namespace)
    // add listener
    ee.$only(
      createEvt(namespace, EMIT_EVT),
      function(resolverName, args) {
        debugFn('calling server', resolverName, args)
        ws.send(
          createQueryStr(resolverName, args)
        )
      }
    )
  }

  // reply
  ws.onmessage = function(payload) {
    try {
      const json = extractWsPayload(payload)
      debugFn('Hear from server', json)
      const { resolverName, type } = json;
      switch (type) {
        case EMIT_REPLY_TYPE:
          let r = ee.$trigger(createEvt(namespace, resolverName, ON_MESSAGE_PROP_NAME), [json])
          debugFn(ON_MESSAGE_PROP_NAME, r)
          break;
        case ACKNOWLEDGE_REPLY_TYPE:
          debugFn(ON_RESULT_PROP_NAME, json)
          let x = ee.$trigger(createEvt(namespace, resolverName, ON_RESULT_PROP_NAME), [json])
          debugFn('onResult add to event?', x)
          break;
        case ERROR_TYPE:
          // this is handled error and we won't throw it
          // we need to extract the error from json
          errorTypeHandler(ee, namespace, resolverName, json)
          break;
        // @TODO there should be an error type instead of roll into the other two types? TBC
        default:
          // if this happen then we should throw it and halt the operation all together
          debugFn('Unhandled event!', json)
          errorTypeHandler(ee, namespace, resolverName, json)
          // let error = {error: {'message': 'Unhandled event!', type}};
          // ee.$trigger(createEvt(namespace, resolverName, ON_RESULT_PROP_NAME), [error])
      }
    } catch(e) {
      errorTypeHandler(ee, namespace, false, e)
    }
  }
  // when the server close the connection
  ws.onclose = function() {
    debugFn('ws.onclose')
    // @TODO what to do with this
    // ee.$trigger(LOGOUT_EVENT_NAME, [namespace])
  }
  // listen to the LOGOUT_EVENT_NAME
  ee.$on(LOGOUT_EVENT_NAME, function close() {
    try {
      debugFn('terminate ws connection')
      ws.terminate()
    } catch(e) {
      debugFn('terminate ws error', e)
    }
  })
}
